# -*- coding: utf-8 -*-
import os
import gc
import struct


try:
	import framebuf
	MICROPYTHON = True
except ImportError:
	MICROPYTHON = False

CURRENT_DIR = os.getcwd() if MICROPYTHON else os.path.dirname(__file__) + '/'
FONT_DIR = '/fonts/'

MBCS_FONT_FILE = ['SIMYOU_M24_CP936.bin']
UNICODE_FONT_FILE = ['SIMYOU_U24.bin']
SIMPLE_UNICODE_FONT_FILE = ['SIMYOU_S24.bin']
# HZK_FILE = 'SIMYOU_M16_CP936.bin' # 这个字库貌似少了一个字，汉字内码偏移了 +1
HZK_FILE = UNICODE_FONT_FILE[0]


class FontLibHeaderException(Exception):
	pass

class FontLibIndexTableException(Exception):
	pass

class FontLibSectionException(Exception):
	pass

class FontLibSectionsException(Exception):
	pass

class FontLibException(Exception):
	pass


'''
Header Data Sample:
	b'UFL\x11xa\x0b\x00\x01\x18\x02\x00A\x1a\x00\x00' # little-endian

	[3] b'UFL'        - encoding
	[1] b'\x11'       - version
	[4] b'xa\x0b\x00' - file length
	[1] b'\x01'       - sections
	[1] b'\x18'       - font height
	[2] b'\x02\x00'   - charset flag
	[2] b'A\x1a'      - character counts
	[2] b'\x00\x00'   - reserved
'''
class FontLibHeader(object):
	ENCODING_MBCS = 0
	ENCODING_UNICODE = 1
	ENCODING_SIMPLE_UNICODE = 2
	ENCODINGS = ['MBCS', 'Unicode', 'Simple-Unicode']
	CHARSET_FLAGS = ['日文', '简中', '韩文', '繁中', '泰文', '中欧', '西里尔', '西欧', '希腊', '土耳其文', '希伯来文', '阿拉伯文', '波罗的海文', '越南文']
	LENGTH = 16

	def __init__(self, header_data):
		if len(header_data) != FontLibHeader.LENGTH:
			raise FontLibHeaderException('Invalid header length')

		self.encoding,\
		self.version,\
		self.file_size,\
		self.sections,\
		self.font_height,\
		self.charset_flags,\
		self.characters,\
		_ = struct.unpack('<3ssIBBHH2s', header_data)

		if self.encoding not in (b'MFL', b'UFL', b'SFL'):
			raise FontLibHeaderException('Invalid font file')

		self.encoding = self.ENCODING_MBCS if self.encoding == b'MFL' else (self.ENCODING_UNICODE if self.encoding == b'UFL' else self.ENCODING_SIMPLE_UNICODE)
		self.version = '.'.join('{:0x}'.format(self.version[0]))

		if self.encoding == self.ENCODING_SIMPLE_UNICODE:
			raise FontLibHeaderException('Not support Simple Unicode font yet')

		# charset_flag_bits = ''.join(reversed('{:0b}'.format(self.charset_flags)))
		# self.charset_flags = [index for index, flag in enumerate(charset_flag_bits) if flag == '1']
		self.charset_flags = [index for index in range(16) if self.charset_flags & (1 << index)]


class FontLibIndexTable(object):
	LENGTH = 4

	def __init__(self):
		pass


'''
Section Data Sample
	b' \x00\xe5\xff\x18\x00\x00\x00' - # little-endian

	[2] b' \x00'            - first
	[2] b'\xe5\xff'         - last
	[4] b'\x18\x00\x00\x00' - offset
'''
class FontLibSection(object):
	LENGTH = 8

	def __init__(self, section_data):
		if len(section_data) != FontLibSection.LENGTH:
			raise FontLibSectionsException('Invalid section length')

		self.first,\
		self.last,\
		self.offset = struct.unpack('<2HI', section_data)

	def info(self):
		print('\
Section Info:\n\
   first : {}\n\
	last : {}\n\
  offset : {}\n'.format(
				self.first,
				self.last,
				self.offset
			))


class FontLibSections(object):
	def __init__(self, sections_data):
		if len(sections_data) % FontLibSection.LENGTH != 0:
			raise FontLibSectionsException('Invalid sections length')

		self.__sections = []

		for index in range(0, len(sections_data), FontLibSection.LENGTH):
			self.__sections.append(FontLibSection(sections_data[index:index + FontLibSection.LENGTH]))

	def get_section(self, unicode: int):
		for section in self.__sections:
			if section.first <= unicode <= section.last:
				return section

		return


class FontLib(object):
	def __init__(self, font_file):
		self.__font_file = font_file
		self.__header = None
		self.__sections = None

		with open(self.__font_file, 'rb') as hzk_file:
			self.__header = FontLibHeader(hzk_file.read(FontLibHeader.LENGTH))

			if self.__header.encoding == FontLibHeader.ENCODING_MBCS and MICROPYTHON:
				raise FontLibException('Font file not suitable for MicroPython')
			elif self.__header.encoding == FontLibHeader.ENCODING_UNICODE and self.__header.sections <= 0:
				raise FontLibException('Invalid font sections amount')

			if self.__header.encoding == FontLibHeader.ENCODING_UNICODE:
				self.__sections = FontLibSections(hzk_file.read(self.__header.sections * FontLibSection.LENGTH))

		gc.collect()

	def __get_mbcs_index(self, char):
		index = -1
		code = bytes(char, 'gbk')

		if 0x20 <= code[0] <= 0x7e:
			return ord(code)

		sector = code[0] # 区码
		bit = code[1] - 1 # 位码

		if 0xa1 <= sector <= 0xfe and 0xa1 <= bit <= 0xfe:
			index = (sector - 0xa1) * 94 + (bit - 0xa1) #  //94 = (0xFE-0xA1+1);
			return index

		return 0

	def __get_character_mbcs_buffer(self, hzk_file, gbk_set):
		buffer_list = []

		for gbk in gbk_set:
			index = self.__get_mbcs_index(gbk)

			if not index:
				raise FontLibException('Can not found this character')

			offset = self.header_length + index * int((self.font_height / 8 * self.font_height))
			data_length = int((self.font_height + 7) / 8 * self.font_height)

			hzk_file.seek(offset)
			buffer = hzk_file.read(data_length)

			buffer_list.append([bytes(gbk, 'gbk'), (self.font_height, self.font_height), buffer])

		return buffer_list

	def __get_character_unicode_buffer(self, hzk_file, unicode_set):
		buffer_list = []

		for unicode in unicode_set:
			section = self.__sections.get_section(unicode)
			info_offset = (unicode - section.first) * FontLibIndexTable.LENGTH + section.offset

			hzk_file.seek(info_offset)
			info_data = hzk_file.read(4)

			info_data = '{:032b}'.format(struct.unpack('<I', info_data)[0])
			font_width = int(info_data[:6], 2)
			char_offset = int(info_data[6:], 2)

			if font_width >= 64:
				raise FontLibException('Not supported character width')
			
			if font_width == 0:
				raise FontLibException('Can not found this character')

			hzk_file.seek(char_offset)
			buffer = hzk_file.read(int(self.font_height * (font_width + 8 - 1) / 8))

			buffer_list.append([unicode, (font_width, self.font_height), buffer])

		return buffer_list

	def get_characters(self, characters: str):
		with open(self.__font_file, 'rb') as hzk_file:
			if self.encoding_flag == FontLibHeader.ENCODING_MBCS:
				gbk_set = set(characters)

				return self.__get_character_mbcs_buffer(hzk_file, gbk_set)
			elif self.encoding_flag == FontLibHeader.ENCODING_SIMPLE_UNICODE:
				pass
			else:
				unicode_set = set()
				for char in characters:
					unicode_set.add(ord(char))

				return self.__get_character_unicode_buffer(hzk_file, unicode_set)

	@property
	def header_length(self):
		return FontLibHeader.LENGTH

	@property
	def version(self):
		return self.__header.version
	
	@property
	def encoding(self):
		return self.__header.ENCODINGS[self.__header.encoding]

	@property
	def encoding_flag(self):
		return self.__header.encoding

	@property
	def sections(self):
		return self.__header.sections

	@property
	def file_size(self):
		return self.__header.file_size
	
	@property
	def font_height(self):
		return self.__header.font_height

	@property
	def charset_flags(self):
		return self.__header.charset_flags

	@property
	def charsets(self):
		return [FontLibHeader.CHARSET_FLAGS[charset] for charset in self.__header.charset_flags]

	@property
	def characters(self):
		return self.__header.characters

	def info(self):
		print('\
HZK Info: {}\n\
	 version  : {}\n\
	file size : {}\n\
	 encoding : {}\n\
	 sections : {}\n\
  font height : {}\n\
	  charset : {}\n\
   characters : {}\n'.format(
			  self.__font_file,
			  self.version,
			  self.file_size,
			  self.encoding,
			  self.sections,
			  self.font_height,
			  self.charsets,
			  self.characters
			))


def is_font_file_exist(font_file):
	try:
		os.stat(font_file)
		return True
	except:
		return False

font_file = CURRENT_DIR + FONT_DIR + HZK_FILE

def run_test():
	if is_font_file_exist(font_file):
		hzk = FontLib(font_file)
		hzk.info()
		buffer_list = hzk.get_characters('Hello123爱我中华')

		for buffer in buffer_list:
			character = None

			if hzk.encoding_flag == FontLibHeader.ENCODING_MBCS:
				character = str(buffer[0], 'gbk')
			else:
				character = chr(buffer[0])

			print("'{}' {}\n".format(character, buffer))

def list_lost_characters():
	hzk = FontLib(font_file)
	hzk.info()
	lost_ascii = []
	lost_hanzi = []

	for char in range(0x21, 0x7F):
		try:
			hzk.get_characters(chr(char))
		except FontLibException as fle:
			if str(fle) == 'Can not found this character':
				lost_ascii.append(chr(char))
	
	for char in range(0x4E00, 0x9FA0):
		try:
			hzk.get_characters(chr(char))
		except FontLibException as fle:
			if str(fle) == 'Can not found this character':
				lost_hanzi.append(chr(char))

	# print(lost_ascii)
	# print(lost_hanzi)

	with open('lost_chars.txt', 'w', encoding='utf-8') as file:
		file.write(f'total counts: {hzk.characters}\n\n')
		file.write(f'ascii length: {len(lost_ascii)}\n')
		file.write(', '.join(lost_ascii))
		file.write('\r\r')
		file.write(f'hanzi length: {len(lost_hanzi)}\n')
		file.write(', '.join(lost_hanzi))


if __name__ == '__main__':
	run_test()
	# list_lost_characters()


'''
# MicroPython Test Code

from machine import I2C, Pin
from ssd1306 import SSD1306_I2C
import framebuf

oled = None

def test_new_array():
	char = bytearray(b'\x00@\x07PxH\x08H\x08@\xff\xfe\x08@\x08H\x0bH\x0c0x0\x08"\x08\xd2\t\nx\x0e\x00\x00\x00\x00\x080~H\x08\x84\x7f\x0bBHB(')
	buffer = framebuf.FrameBuffer(char, 16, 16, framebuf.MONO_HLSB) # 注意不是 MONO_VLSB
	oled.fill(0)
	oled.blit(buffer, 20, 20)
	oled.show()


def main():
	global oled

	i2c = I2C(0, scl=Pin(18), sda=Pin(19))
	slave_list = i2c.scan()

	if slave_list:
		print('slave id: {}'.format(slave_list[0]))
		oled = SSD1306_I2C(128, 64, i2c)

		test_new_array()

if __name__ == "__main__":
	main()
'''